﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using BobcatMultiTouch;
using System.Timers;
using BobcatUtility;
using CNUtility;

namespace CNShapeEditor
{
    /// <summary>
    /// This gesture class checks whether we want to start dragging the picture. To
    /// do this it checks for a click-and-hold 'gesture'. If this is detected, it
    /// raises an event to ask that the app start to use the Action_DragPicture action.
    /// </summary><remarks>
    /// 
    /// To check for the click-and-hold we:
    /// 
    /// - Wait for the first touch-down.
    /// 
    /// - Start a timer for the hold period.
    /// 
    /// - During the hold period we see if there are any touch-move events.
    ///   If so, we see how far we moved from the initial touch. If we've moved 
    ///   too far, the gesture has not happened and we can kill the timer.
    ///   
    /// - During the hold period we see if there are any touch-up events.
    ///   If so the gesture has not happened.
    ///   
    /// - If the timer has ticked without the gesture being canceled, then
    ///   we have detected the gesture and we can raise an event.
    ///   
    /// </remarks>
    public class GestureHandler_DragPicture : GestureHandler
    {
        #region Public methods

        /// <summary>
        /// Called to set up the gesture.
        /// </summary>
        public override void initialize()
        {
        }

        /// <summary>
        /// Called to tell us that a new action has started. We reset the gesture.
        /// </summary>
        public override void onNewAction()
        {
            m_GestureCompleted = false;
        }

        /// <summary>
        /// Called when we get a touch-down event.
        /// </summary>
        public override void onTouchDown(TouchArgs touchArgs)
        {
            // If the gesture has been completed, we just ignore the event...
            if (m_GestureCompleted == true)
            {
                return;
            }

            // There should be only one touch detected for this event...
            IList<TouchArgs> listTouches = m_ShapeEditorControl.MTIC.getActiveTouches();
            if (listTouches.Count != 1)
            {
                cancelGesture();
                return;
            }

            // We want to start checking for the click-and-hold. So we
            // store the touch-position and start a timer for the hold period...
            m_TouchDownPoint = Utility.copy(touchArgs.ClientPosition);
            int iHoldInterval = UserPreferences.getPropertyBag().getInt("GestureHandler_DragPicture.PressAndHoldInterval");
            m_Timer = new Timer(iHoldInterval);
            m_Timer.AutoReset = false;
            m_Timer.Elapsed += onTimerElapsed;
            m_Timer.Enabled = true;
        }

        /// <summary>
        /// Called when a touch-move event occurs.
        /// </summary>
        public override void onTouchMove(TouchArgs touchArgs)
        {
            // If the gesture has been completed, we just ignore the event...
            if (m_GestureCompleted == true)
            {
                return;
            }

            // We see how far we've moved from the initial touch-down point.
            // If we've moved too far it looks like we've not got a click-and-hold
            // and we'll cancel the gesture...
            int iXOffset = touchArgs.ClientPosition.X - m_TouchDownPoint.X;
            int iYOffset = touchArgs.ClientPosition.Y - m_TouchDownPoint.Y;
            int iDistanceSquared = iXOffset * iXOffset + iYOffset * iYOffset;
            if (iDistanceSquared > MAX_DISTANCE_SQUARED)
            {
                cancelGesture();
            }
        }

        /// <summary>
        /// Called when a touch-up event is received.
        /// </summary>
        public override void onTouchUp(TouchArgs touchArgs)
        {
            // If the gesture has been completed, we just ignore the event...
            if (m_GestureCompleted == true)
            {
                return;
            }

            // If the touch-up has occured before the timer (which is the case if we are here)
            // then we cancel the event...
            cancelGesture();
        }

        #endregion

        #region Private functions

        /// <summary>
        /// We cancel the gesture.
        /// </summary>
        public void cancelGesture()
        {
            if (m_Timer != null)
            {
                m_Timer.Enabled = false;
                m_Timer = null;
            }
            m_GestureCompleted = true;
        }

        /// <summary>
        /// Called when the timer has elapsed.
        /// </summary>
        private void onTimerElapsed(object sender, ElapsedEventArgs e)
        {
            // If the timer has elapsed, this means that we've detected the gesture.
            m_GestureCompleted = true;

            // We create and initialize the new action...
            Action_DragPicture action = new Action_DragPicture();
            action.initialize(m_Page, m_ShapeEditorControl);

            // We simulate the initial touch-down (as the touch-down has already happenend
            // as part of this gesture)...
            TouchArgs touchArgs = new TouchArgs();
            touchArgs.ClientPosition = Utility.copy(m_TouchDownPoint);
            action.onTouchDown(touchArgs);

            // We create the args for the event we're raising...
            Args args = new Args();
            args.NewAction = action;
            args.RevertToPreviousActionWhenComplete = true;
            raiseGestureDetectedEvent(args);
        }

        #endregion

        #region Private data

        private const int MAX_DISTANCE_SQUARED = 8; // The max distance (squared) that the input can move and still be deemed to be "held".
        private bool m_GestureCompleted = false;
        private Point m_TouchDownPoint = new Point();
        private Timer m_Timer = null;

        #endregion
    }
}
